﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace KentekenHerkenning
{
    /// <summary>
    /// The class for our OCR algorithm.
    /// This class contaile to contains functions which will be needed to read the test-set, and determine 
    /// the masks / sequences for the algorithm
    /// 
    /// Made by Alexander Streng, Edited by Dylan Snel
    /// </summary>
   public static class Awesome_O_CR
    {
       /// <summary>
       /// The function to be called when the algorithm is started. Has an bitmap for input,
       /// which needs to contain a single cropped black character on a white background.
       /// First the image is made binairy, and set to a 2d - array of ints ( 1 = black , 0 = white )
       /// </summary>
       /// <param name="b">The bitmap to run the alogrithm on</param>
       /// <param name="path">the path to the image ( not required )</param>
       /// <param name="testSet">The list of test - set images to compare the image to</param>
       /// <returns>A list with possible candidates for the given character</returns>
       public static List<TestsetCharacter> doAlgorithm(Bitmap b, string path, List<TestsetCharacter> testSet)
       {
           List<TestsetCharacter> temp = new List<TestsetCharacter>();

           int[,] Points = new int[b.Width, b.Height];

           Points = Binaryze.MakeBinaryIntImageInverted(b);

           TestsetCharacter desigNationCharacter = new TestsetCharacter(path,path);

           desigNationCharacter = GetDesginationCharacterSequence(Points, b.Width, b.Height, "adfuasdfu_asf");

           temp = HistogramCompare(desigNationCharacter, testSet);

           return temp;
       }

       /// <summary>
       /// Sets the dark - light sequence of the algorithm for each given image
       /// Requires the 2d array of the image ( black / white )
       /// </summary>
       /// <param name="points">The 2d array of the image</param>
       /// <param name="Width">the width of the image</param>
       /// <param name="Height">the height of the image</param>
       /// <param name="name">The name to be given to the characters</param>
       /// <returns>The TesetCharacter object which holds the sequences</returns>
       public static TestsetCharacter GetDesginationCharacterSequence(int[,] points, int Width, int Height, string name)
       {
           TestsetCharacter temp = new TestsetCharacter(name, name);

           //Get the sequence values for the designationCharacter, and returns them as a TestsetCharacter Class
           // 0 = light area, 1 = dark area

           int[,] bottomMask = new int[Width, Height / 4];
           int[,] topMask = new int[Width, Height / 4];
           int[,] rightMask = new int[Width / 4, Height];
           int[,] leftMask = new int[Width / 4, Height];

           //bottomMask - onderste 1/4 van points int[,]
           //leftMask - linker 1/4 van points int[,]
           //rightMask - rechter 1/4 van points int[,]
           //topMask - bovenste 1/4 van points int[,]

           int[] LineVerti = new int[Height];
           int[] LineHoriTop = new int[Width];
           int[] LineHoriMid = new int[Width];
           int[] LineHoriBottom = new int[Width];

           //verticalLine - verticale lijn op 1/2 van points int[]
           //horizontalLineTop - horizontale lijn op 1/3 van points int[]
           //horizontalLineBottom - horizontale lijn op 2/3 van points int[]

           int rightOffset = Width - (Width / 4);
           int bottomOffset = Height - (Height / 4);

           for (int y = 0; y < Height; y++)
           {
               for (int x = 0; x < Width; x++)
               {

                   if (y == (Height / 3) * 1)
                   {
                       LineHoriTop[x] = points[x, y];
                   }

                   if (y == (Height / 3) * 2)
                   {
                       LineHoriBottom[x] = points[x, y];
                   }
                   if (y == (Height / 2))
                   {
                       LineHoriMid[x] = points[x, y];
                   }

                   if (x == (Math.Abs(Width / 2)))
                   {
                       LineVerti[y] = points[x, y];
                   }

                   if (y < ( Height / 4 ) )//topMask Height / 4
                   {
                       topMask[x, y] = points[x, y];
                   }

                   if (x < ( Width / 4 ))//leftMask Width / 4
                   {
                       leftMask[x, y] = points[x, y];
                   }

                   if (x > rightOffset) //rightMask Width / 4
                   {
                       rightMask[x - rightOffset, y] = points[x, y];
                   }

                   if (y > bottomOffset) // BottomMask Height / 4
                   {
                       bottomMask[x, y - bottomOffset] = points[x, y];
                   }
               }
           }

           temp.TopHorizontalSlice = SetSequencesLines(LineHoriTop);
           temp.MidHorizontalSlice = SetSequencesLines(LineHoriMid);
           temp.BottomHorizontalSlice = SetSequencesLines(LineHoriBottom);
           temp.VerticalSlice = SetSequencesLines(LineVerti);

           temp.BottomMask = SetSequencesMasks(bottomMask, Width, Height / 4, true);
           temp.RightMask = SetSequencesMasks(rightMask, Width / 4, Height, false);
           temp.LeftMask = SetSequencesMasks(leftMask, Width / 4, Height, false);
           temp.TopmMask = SetSequencesMasks(topMask, Width, Height / 4, true);

         //  Console.WriteLine(temp.SequencesToString() + "\n\n");

        //   Console.WriteLine("All done!");

           return temp;
       }

       /// <summary>
       /// Determine the dark - light sequences from the given int array lines. This means checking every int in the line.
       /// and determing the sequence from it. A possible section in the array could be '11111111000000000111111' which
       /// would give the sequence -> 1 0 1 or light dark light
       /// </summary>
       /// <param name="lines">The int array which contains the values to be checked</param>
       /// <returns>The list of calculated sequences</returns>
       public static List<int> SetSequencesLines(int[] lines)
       {
           List<int> returnList = new List<int>();

               int currentId = lines[0];

               foreach (int pixelToCheck in lines)
               {
                   if (pixelToCheck == 0 && pixelToCheck == currentId)
                   {
                       returnList.Add(pixelToCheck);
                       currentId = 1;
                   }
                   if (pixelToCheck == 1 && pixelToCheck == currentId)
                   {
                       returnList.Add(pixelToCheck);
                       currentId = 0;
                   }
               }

           return returnList;
       }

       /// <summary>
       /// Determine the dark - light sequences from the given 2d int array Mask. This means checking every int in the line.
       /// and determing the sequence from it. A possible section in the array could be '11111111000000000111111' which
       /// would give the sequence -> 1 0 1 or light dark light
       /// </summary>
       /// <param name="Mask">the double int array containing the ints</param>
       /// <param name="width">Width of the image</param>
       /// <param name="height">Height of the image</param>
       /// <param name="horizontal">Boolean to see if its a horizontal mask or a vertical one</param>
       /// <returns>The list of calculated sequences</returns>
       public static List<int> SetSequencesMasks(int[,] Mask, int width, int height, bool horizontal)
       {
           if (!horizontal)
           {
               List<int> tempList = new List<int>();
               int[] tempInt = new int[height];

               for (int y = 0; y < height; y++)
               {
                   for (int x = 0; x < width; x++)
                   {
                       if (Mask[x, y] == 1)
                       {
                           tempInt[y] = 1;
                           continue;
                       }
                   }
               }
               return SetSequencesLines(tempInt); 
           }
           else
           {
               List<int> tempList = new List<int>();
               int[] tempInt = new int[width];

               for (int x = 0 ; x < width; x++)
               {
                   for (int y = 0; y < height; y++)
                   {
                       if (Mask[x, y] == 1)
                       {
                           tempInt[x] = 1;
                           continue;
                       }
                   }
               }

               return SetSequencesLines(tempInt);

           }
       }

       /// <summary>
       /// The function to read the whole testset. Needs to contain single cropped images
       /// </summary>
       /// <param name="imgList">The List containing the bitmaps</param>
       /// <returns></returns>
       public static List<TestsetCharacter> readTestSet(List<Bitmap> imgList)
       {

           List<TestsetCharacter> testSetList = new List<TestsetCharacter>();

           Console.WriteLine("So it begins..");

           foreach (Bitmap b in imgList)
           {
               Bitmap Tempmap = b.cropBlack();
               //Tempmap = Tempmap.addWhiteEdge(5);

               int[,] Points = new int[Tempmap.Width, Tempmap.Height];

               Points = Binaryze.MakeBinaryIntImageInverted(Tempmap);
               string name = (string) b.Tag;
               //Console.WriteLine("Name = " + "_" + name.Substring(name.LastIndexOf("\\") + 1));
               TestsetCharacter desigNationCharacter = new TestsetCharacter((string)b.Tag, "_"+ name.Substring(name.LastIndexOf("\\")+1));

             //  Console.WriteLine("w: " + Tempmap.Width + " - h: " + Tempmap.Height + "  : Points length:" + Points.Length);

             //  Console.WriteLine("ingelezen: " + (string)b.Tag);

               
               desigNationCharacter = GetDesginationCharacterSequence(Points, Tempmap.Width, Tempmap.Height, (string)b.Tag);

               testSetList.Add(desigNationCharacter);
           }
            Console.WriteLine("Alles ingelezen, aantal chars in de testset: " +  testSetList.Count());
            return testSetList;
       }

       /// <summary> * DEPRICATED *
       /// Does the binary deciscion tree to determine which character is most likely the one.
       /// We stopped using this function because of the false positives it somethimes gave.
       /// </summary>
       /// <param name="designNationCharacter">the character to compare</param>
       /// <param name="TestSet">the list of characters to compare with.</param>
       /// <returns>A list with possible candidates for the given character</returns>
       public static List<TestsetCharacter> DoDecisionTree(TestsetCharacter designNationCharacter, List<TestsetCharacter> TestSet)
       {
           //TestSet list - TestSetCharacter

           //doe de decision tree ( 7 levels )
           // als de subset al eerder in de tree niet groter is dan 1 break;
           //als de subset al eerder de tree 0 is -> Ga met de subset van stap n-1 verder

           List<TestsetCharacter> subSet1 = new List<TestsetCharacter>();
           List<TestsetCharacter> subSet2 = new List<TestsetCharacter>();
           //Console.WriteLine("testSet length: " + TestSet.Count);

           //Stap 1 leftmask
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.LeftMask.SequenceEqual(designNationCharacter.LeftMask))
               {
                   subSet1.Add(checkChar);
               }
           }

           //Console.WriteLine("LeftMask subset length: " + subSet1.Count);
           if (subSet1.Count == 1)
           {
               return subSet1;
           }


           
           //stap 2 topmask
           foreach (TestsetCharacter checkChar in subSet1)
           {
               if (checkChar.TopmMask.SequenceEqual(designNationCharacter.TopmMask))
               {
                   subSet2.Add(checkChar);
               }
           }

           //Console.WriteLine("TopMask subset length: " + subSet2.Count);
           if (subSet1.Count == 1)
           {
               return subSet1;
           }

           
           subSet1.Clear();
           //stap 3 rightmask
           foreach (TestsetCharacter checkChar in subSet2)
           {
               if (checkChar.RightMask.SequenceEqual(designNationCharacter.RightMask))
               {
                   subSet1.Add(checkChar);
               }
           }

           //Console.WriteLine("RightMask subset length: " + subSet1.Count);
           if (subSet2.Count == 1)
           {
               return subSet1;
           }

           
           subSet2.Clear();
           //stap 4 bottommask
           foreach (TestsetCharacter checkChar in subSet1)
           {
               if (checkChar.BottomMask.SequenceEqual(designNationCharacter.BottomMask))
               {
                   subSet2.Add(checkChar);
               }
           }

           //Console.WriteLine("Bottom subset length: " + subSet2.Count);
           if (subSet1.Count == 1)
           {
               return subSet1;
           }

           
           subSet1.Clear();
           //stap 5 top horizontal line
           foreach (TestsetCharacter checkChar in subSet2)
           {
               if (checkChar.TopHorizontalSlice.SequenceEqual(designNationCharacter.TopHorizontalSlice))
               {
                   subSet1.Add(checkChar);
               }
           }

           //Console.WriteLine("Horizontal line subset length: " + subSet1.Count);
           if (subSet2.Count == 1)
           {
               return subSet1;
           }

           
           subSet2.Clear();
           //stap 6 bottom horizontal line
           foreach (TestsetCharacter checkChar in subSet1)
           {
               if (checkChar.BottomHorizontalSlice.SequenceEqual(designNationCharacter.BottomHorizontalSlice))
               {
                   subSet2.Add(checkChar);
               }
           }

           //Console.WriteLine("Bottom horizontal line subset length: " + subSet2.Count);
           if (subSet1.Count == 1)
           {
               return subSet1;
           }
           
           subSet1.Clear();
           //stap 7 vertical line
           foreach (TestsetCharacter checkChar in subSet2)
           {
               if (checkChar.VerticalSlice.SequenceEqual(designNationCharacter.VerticalSlice))
               {
                   subSet1.Add(checkChar);
               }
           }

           //Console.WriteLine("Horizontal line subset length: " + subSet1.Count);
           if (subSet2.Count == 1)
           {
               return subSet1;
           }
           
           subSet2.Clear();
           //stap 6 bottom horizontal line
           foreach (TestsetCharacter checkChar in subSet1)
           {
               if (checkChar.MidHorizontalSlice.SequenceEqual(designNationCharacter.MidHorizontalSlice))
               {
                   subSet2.Add(checkChar);
               }
           }
        
           //Console.WriteLine("Vertical line subset length: " + subSet2.Count);
           return subSet2;
       }

       /// <summary>
       /// The decision tree based on histograms to compare the given character with the given testset.
       /// Basically weighs each character to see how likely it is that it matches the the given character.
       /// </summary>
       /// <param name="designNationCharacter">the character to compare</param>
       /// <param name="TestSet">the list of characters to compare with.</param>
       /// <returns>A list with possible candidates for the given character</returns>
       public static List<TestsetCharacter> HistogramCompare(TestsetCharacter designNationCharacter, List<TestsetCharacter> TestSet)
       {

           Dictionary<string, int> DT = new Dictionary<string, int>();

          
           //Console.WriteLine("testSet length: " + TestSet.Count);

           //Stap 1 leftmask
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.LeftMask.SequenceEqual(designNationCharacter.LeftMask))
               {
                   if (DT.ContainsKey(checkChar.Character))
                   {
                       DT[checkChar.Character]++;
                   }
                   else
                   {
                       DT.Add(checkChar.Character, 0);
                   }
               }
           }

           



           //stap 2 topmask
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.TopmMask.SequenceEqual(designNationCharacter.TopmMask))
               {
                   if (DT.ContainsKey(checkChar.Character))
                   {
                       DT[checkChar.Character]++;
                   }
                   else
                   {
                       DT.Add(checkChar.Character, 0);
                   }
               }
           }

           


           //stap 3 rightmask
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.RightMask.SequenceEqual(designNationCharacter.RightMask))
               {
                   if (DT.ContainsKey(checkChar.Character))
                   {
                       DT[checkChar.Character]++;
                   }
                   else
                   {
                       DT.Add(checkChar.Character, 0);
                   }
               }
           }

           //Console.WriteLine("RightMask subset length: " + subSet1.Count);
           
           //stap 4 bottommask
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.BottomMask.SequenceEqual(designNationCharacter.BottomMask))
               {
                   if (DT.ContainsKey(checkChar.Character))
                   {
                       DT[checkChar.Character]++;
                   }
                   else
                   {
                       DT.Add(checkChar.Character, 0);
                   }
               }
           }

           //Console.WriteLine("Bottom subset length: " + subSet2.Count);
           


           //stap 5 top horizontal line
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.TopHorizontalSlice.SequenceEqual(designNationCharacter.TopHorizontalSlice))
               {
                   if (DT.ContainsKey(checkChar.Character))
                   {
                       DT[checkChar.Character]++;
                   }
                   else
                   {
                       DT.Add(checkChar.Character, 0);
                   }
               }
           }

           //Console.WriteLine("Horizontal line subset length: " + subSet1.Count);
           

           //stap 6 bottom horizontal line
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.BottomHorizontalSlice.SequenceEqual(designNationCharacter.BottomHorizontalSlice))
               {
                   if (DT.ContainsKey(checkChar.Character))
                   {
                       DT[checkChar.Character]++;
                   }
                   else
                   {
                       DT.Add(checkChar.Character, 0);
                   }
               }
           }

           //Console.WriteLine("Bottom horizontal line subset length: " + subSet2.Count);
          
           
           //stap 7 vertical line
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.VerticalSlice.SequenceEqual(designNationCharacter.VerticalSlice))
               {
                   if (DT.ContainsKey(checkChar.Character))
                   {
                       DT[checkChar.Character]++;
                   }
                   else
                   {
                       DT.Add(checkChar.Character, 0);
                   }
               }
           }

           //Console.WriteLine("Horizontal line subset length: " + subSet1.Count);
           

           //stap 6 bottom horizontal line
           foreach (TestsetCharacter checkChar in TestSet)
           {
               if (checkChar.MidHorizontalSlice.SequenceEqual(designNationCharacter.MidHorizontalSlice))
               {
                   if (DT.ContainsKey(checkChar.Character))
                   {
                       DT[checkChar.Character]++;
                   }
                   else
                   {
                       DT.Add(checkChar.Character, 0);
                   }
               }
           }
            List<TestsetCharacter> FinalList = new List<TestsetCharacter>();
           int maxKey = 2;
           bool result = false;
           string maxResult = "";
           foreach (string s in DT.Keys)
           {
               if (DT[s] > maxKey)
               {
                   maxKey = DT[s];
                   result = true;
                   maxResult = s;
               }
           }
           if (result)
           {
               TestsetCharacter t = new TestsetCharacter("", "_" + maxResult + ".jpg");
               FinalList.Add(t);
           }


                     
           //Console.WriteLine("Vertical line subset length: " + subSet2.Count);
           return FinalList;
       }

    }
}
